package com.xqboss.apps.service.guild;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xqboss.apps.constant.Config;
import com.xqboss.apps.domain.guild.Guild;
import com.xqboss.apps.domain.guild.GuildApplyRecord;
import com.xqboss.apps.domain.guild.GuildInviteRecord;
import com.xqboss.apps.domain.guild.GuildMember;
import com.xqboss.apps.dto.guild.*;
import com.xqboss.apps.enums.consumer.ConsumerTeamRoleEnum;
import com.xqboss.apps.enums.consumer.InteractMsgEnum;
import com.xqboss.apps.enums.guild.GuildApplyConsumeStatusEnum;
import com.xqboss.apps.enums.guild.GuildApplyOrInviteStatusEnum;
import com.xqboss.apps.enums.guild.GuildExitStatusEnum;
import com.xqboss.apps.enums.guild.GuildExitTypeEnum;
import com.xqboss.apps.enums.user.UserWalletEnum;
import com.xqboss.apps.enums.user.WalletChangeTypeEnum;
import com.xqboss.apps.mapper.guild.GuildMapper;
import com.xqboss.apps.service.consumer.ConsumerInteractMsgService;
import com.xqboss.apps.service.consumer.ConsumerRealNameService;
import com.xqboss.apps.service.user.SysUserWalletService;
import com.xqboss.apps.service.user.UserIdentityPermGuildService;
import com.xqboss.apps.vo.guild.*;
import com.xqboss.common.constant.CacheConstants;
import com.xqboss.common.constant.HttpStatus;
import com.xqboss.common.core.domain.R;
import com.xqboss.common.core.redis.RedisCache;
import com.xqboss.common.exception.ServiceException;
import com.xqboss.common.utils.SecurityUtils;
import com.xqboss.common.utils.SocketUtil;
import com.xqboss.common.utils.StringUtils;
import com.xqboss.system.service.ISysConfigService;
import com.xqboss.system.service.ISysUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 公会APP接口
 *
 * @author wangcong
 * @date 2023-11-01
 */
@Slf4j
@Service
public class GuildAppService extends ServiceImpl<GuildMapper, Guild> {
    @Autowired
    private ISysUserService userService;
    @Autowired
    private UserIdentityPermGuildService userIdentityPermGuildService;//用户身份权益配置
    @Autowired
    private GuildMemberService guildMemberService;//公会成员
    @Autowired
    private ConsumerRealNameService consumerRealNameService;//实名
    @Autowired
    private ISysConfigService sysConfigService;//系统配置参数
    @Autowired
    private GuildApplyRecordService guildApplyRecordService;//我的申请（会员）
    @Autowired
    private GuildInviteRecordService guildInviteRecordService;//邀请我的（会长）
    @Autowired
    private SysUserWalletService sysUserWalletService;//消耗热豆
    @Autowired
    private ConsumerInteractMsgService consumerInteractMsgService;//互动消息
    @Autowired
    private RedisCache redisCache;//redis

    //*************************************************************************************************************
    //************************************TODO：下面是APP相关********************************************************
    //*************************************************************************************************************

    /**
     * 推荐公会列表
     *
     * 1、查找：精确搜索，检索公会名称或公会ID，单击搜索框隐藏提示文字调起输入法输入，点击查找按钮后检索内容，输入状态显示×，单击清除搜索框已填内容，检索结果为空时显示占位图，搜索文字内容为空时重置搜索；
     *
     * 2、列表：默认按公会创建时间倒序排序，列表数据为空时显示占位图：还没有推荐的公会
     *
     * 3、确定操作状态（1-可申请 2-已满员 3-已加入）
     * @param dto 传输对象
     * @param userId 用户id
     * @return 推荐公会列表
     */
    public IPage<GuildRecommendVo> guildRecommendList(GuildRecommendDto<GuildRecommendVo> dto, Long userId) {
        GuildMember gm = this.guildMemberService.getGuildMemberInfo(userId);
        Long guildId = gm==null ? 0 : gm.getGuildId();
        return this.getBaseMapper().guildRecommendList(dto.newPage(),guildId, dto.getSearch());
    }


    /**
     * 红点提示-【邀请我的】存在待处理邀请记录时显示
     *
     * @param userId 用户id
     * @return true-【邀请我的】存在待处理邀请记录时显示
     */
    public boolean isHaveApprove(Long userId) {
        Optional.ofNullable(userId).orElseThrow(()->new ServiceException("用户id不能为空！"));
        Long count = this.guildInviteRecordService.lambdaQuery().eq(GuildInviteRecord::getInvitedUserId, userId).eq(GuildInviteRecord::getInviteStatus, GuildApplyOrInviteStatusEnum.APPROVED).count();
        return count!=0;
    }


    /**
     * 创建公会判断
     *
     * 1.校验用户身份后台权益配置是否【已开启创建公会开关】，若未开启提示：xx以上身份才能创建公会（校验提示并列时优先提示该条，取已开启创建公会的最低用户身份，若所有身份都已关闭创建公会则提示：公会创建维护中~）
     *
     * 2.若已在公会中，单击按钮提示：已在公会中，请退出后再创建
     *
     * 3.若不在公会中，且用户身份创建公会权益开关已开启，单击按钮跳转【创建公会】页面 ，返回 消耗的材料信息（创建）
     *
     * @param userId 用户id
     * @return GuildCreateVo
     */
    public GuildCreateConsumeVo createGuildJudge(Long userId) {
        // 1. 进入创建公会逻辑
        // 1.1 判断创建公会的用户是否实名
        Assert.notNull(this.consumerRealNameService.getUserRealNameOrNull(userId),()->new ServiceException("实名拥有身份后才能创建~"));
        // 1.2 校验用户身份
        GuildCreateConsumeVo vo = this.userIdentityPermGuildService.getGuildCreateConsume(userId);
        // 1.3 校验用户已在某个公会中
        GuildMember member = this.guildMemberService.getGuildMemberInfo(userId);
        Assert.isFalse(ObjUtil.isNotEmpty(member),()->new ServiceException(member.getRole()==ConsumerTeamRoleEnum.ADMINISTRATOR?"您已在创建的公会中~":"已在公会中，请退出后再创建~"));
        // 1.4 返回消耗的材料信息
        return vo;
    }

    /**
     * 我的公会详情
     *
     * @param userId 会长用户id
     * @return 公会详情
     */
    public GuildCreateVo manageGuildDetail(Long userId) {
        //1.获取公会信息
        Guild guild = getGuildOrNull(userId);
        Assert.notNull(guild,()->new ServiceException("未查询到公会信息~"));
        //2.返回vo
        return GuildCreateVo.builder()
                .id(guild.getId())
                .name(guild.getName())
                .manifesto(guild.getManifesto())
                .editConsume(this.sysConfigService.getValue(Config.Sys.Guild.编辑公会名称热豆消耗数量))
                .isMaxMember(guild.getMemberCount()>=guild.getMaxNumber())
                .isNeedVerify(guild.getNeedVerify())
                .isTransferGuild(guild.getMemberCount()>=2)
                .build();
    }

    /**
     * 校验公会名称

     * 公会名称不重复
     *
     * @param guildName 公会名称
     * @param userId 用户id
     */
    public void checkGuildName(String guildName,Long userId) {
        Assert.isFalse(this.lambdaQuery().eq(Guild::getName,guildName).eq(Guild::getStatus,0).count()!=0,()->new ServiceException("名称重复，换个试试~"));
    }

    /**
     * 创建公会（确认创建-若后台配置了消耗材料，会返回材料信息
     *
     *
     * @param dto 传输对戏
     * @param userId 用户id
     */
    @Transactional(rollbackFor = Exception.class)
    public GuildCreateConsumeVo createGuildConfirm(GuildCreateDto dto, Long userId) {
        //  判断用户是否已在公会
        Assert.isFalse(this.guildMemberService.getIsJoinGuild(userId),()->new ServiceException("请退出公会后再创建~"));
        //1 校验公会名称是否重复
        this.checkGuildName(dto.getName(),userId);
        //2 创建公会判断，获取创建公会要消耗的材料
        GuildCreateConsumeVo consumeVo = this.createGuildJudge(userId);
        //3 若没有配置了可消耗的材料,则直接创建公会
        if (ObjUtil.isEmpty(consumeVo)) {
            this.createGuildAndMember(dto,userId,Boolean.FALSE);
        }
        //4 创建公会配置了材料信息，则返回前端，待用户确认
        return consumeVo;
    }


    /**
     * 创建公会（确认-消耗材料
     *
     * @param dto 传输对戏
     * @param userId 用户id
     */
    @Transactional(rollbackFor = Exception.class)
    public void createGuildConfirmConsume(GuildCreateDto dto, Long userId) {
        //  判断用户是否已在公会
        Assert.isFalse(this.guildMemberService.getIsJoinGuild(userId),()->new ServiceException("请退出公会后再创建~"));
        //1 校验公会名称是否重复
        this.checkGuildName(dto.getName(),userId);
        //2 创建公会判断，获取创建公会要消耗的材料
        GuildCreateConsumeVo consumeVo = this.createGuildJudge(userId);
        //3 若配置了可消耗的材料,先把材料消耗了，再创建公会
        this.userIdentityPermGuildService.startConsume(consumeVo,userId,Boolean.TRUE);
        //4 消耗材料成功，为该用户创建公会，他为会长
        this.createGuildAndMember(dto,userId,Boolean.TRUE);
    }


    /**
     * 用户申请公会
     *
     * 1.校验是否实名，未实名没有用户身份无法申请加入，单击按钮提示：实名拥有身份后才能申请（并行时，优先提示该条）
     *
     * 2.若已在公会中单击【申请按钮】提示：已在公会中，请退出后再申请
     *
     * 3.加入公会冷却时间：主动退出公会24小时内不能申请加入公会（会长剔除会员，会员不受限制、会长解散公会，会长受申请加入限制（会长记为主动退出），创建公会不受该规则限制），单击申请按钮提示：冷却中，主动退出公会24小时后才能再次加入（优先提示），后台解散公会（记为非主动退出），所有成员包括会长不受冷却时间限制
     *
     * 4.判断后台有无配置对应用户身份加入公会所要消耗材料，有则返回材料信息 && 还需要判断申请的公会有无开启审核
     *
     * @param guildId 申请的公会id
     * @param userId 申请的用户id
     * @return 如果配置了加入公会需要消耗的材料则返回材料信息
     */
    @Transactional(rollbackFor = Exception.class)
    public R<Object> applyJoinGuild(Long guildId,Long userId) {
        //1 判断申请公会的用户是否实名
        Assert.notNull(this.consumerRealNameService.getUserRealNameOrNull(userId),()->new ServiceException("实名拥有身份后才能申请~"));
        //2 判断是否已在某个公会
        GuildMember member = this.guildMemberService.getGuildMemberInfo(userId);
        Assert.isFalse(ObjUtil.isNotEmpty(member),()->new ServiceException(member.getRole()==ConsumerTeamRoleEnum.ADMINISTRATOR?"会长不能直接加入其他公会~":"已在公会中，请退出后再申请~"));
        //3 获取申请的公会信息
        Guild guild = this.getGuildInfo(guildId);
        // 当前成员数大于等于最大成员数，满员
        Assert.isFalse(guild.getMemberCount()>=guild.getMaxNumber(),()->new ServiceException("申请公会已满员~"));
        //4 主动退出公会冷却时间24小时
        GuildExitStatusEnum exitStatus = this.guildMemberService.getLastExitGuildInfo(userId);
        //5 获取加入公会要消耗的材料
        GuildCreateConsumeVo vo = this.userIdentityPermGuildService.getGuildJoinConsume(userId);
        //6 进行判断
        // 是否配置了加入公会消耗材料
        boolean noGuildJoinConsume = ObjUtil.isEmpty(vo);
        // 是否需要会长进行审核
        boolean isNeedVerify = guild.getNeedVerify();
        // 是否存在之前的申请为待通过 或 已拒绝 或 已超时，且该次申请被消耗了材料，若消耗了材料，那么申请或加入其它公会将不消耗材料; 一句话：消耗了材料，还没有加入到公会，之后的申请将不消耗材料，但申请记录要记为已消耗材料
        boolean isApplyUnJoined = this.guildApplyRecordService.isApplyUnJoined(userId);
        //6.1 - 若不在公会中且前一次主动退出公会，且后台【未配置】申请加入公会材料消耗，且公会【未开启】加入审核，单击【申请按钮】直接加入公会成功，提示：加入公会成功，并跳转到【公会】页面
        boolean a = exitStatus == GuildExitStatusEnum.EXIT_ACTIVE && noGuildJoinConsume && !isNeedVerify;
        //6.2 - 若不在公会中且前一次主动退出公会，且后台【未配置】申请加入公会材料消耗，且公会【已开启】加入审核，单击【申请按钮】发送加入申请，提示：已发送加入申请
        boolean a1 = exitStatus == GuildExitStatusEnum.EXIT_ACTIVE && noGuildJoinConsume && isNeedVerify;
        //6.3 - 若不在公会中且前一次非主动退出公会或申请被拒绝，单击【申请按钮】后不消耗材料，若公会【未开启】加入审核，单击【申请按钮】直接加入公会成功，提示：加入公会成功，并跳转到【公会】页面
        boolean b = (exitStatus == GuildExitStatusEnum.EXIT_ACTIVE_UN && !isNeedVerify) || (isApplyUnJoined && !isNeedVerify);
        //6.4 - 若不在公会中且前一次非主动退出公会或申请被拒绝，单击【申请按钮】后不消耗材料，若公会【已开启】加入审核，单击【申请按钮】发送加入申请，提示：已发送加入申请
        boolean b1 = (exitStatus == GuildExitStatusEnum.EXIT_ACTIVE_UN && isNeedVerify) || (isApplyUnJoined && isNeedVerify);
        //6.5 - 若是第一次加入公会，且后台【未配置】申请加入公会材料消耗，且公会【未开启】加入审核，单击【申请按钮】直接加入公会成功，提示：加入公会成功，并跳转到【公会】页面
        boolean c = exitStatus == GuildExitStatusEnum.FIRST_JOIN_GUILD && noGuildJoinConsume && !isNeedVerify;
        //6.6 - 若是第一次加入公会，且后台【未配置】申请加入公会材料消耗，且公会【已开启】加入审核，单击【申请按钮】发送加入申请，提示：已发送加入申请
        boolean c1 = exitStatus == GuildExitStatusEnum.FIRST_JOIN_GUILD && noGuildJoinConsume && isNeedVerify;
        //6.7 - 之前只要发送了加入申请A，且申请状态为待通过，并消耗了材料；那么在加入申请A通过之前 的 任何加入申请都将不消耗材料
        // 不需要审核，直接加入公会
        if (a || b || c) {
            // 直接加入公会
            this.guildMemberService.createMember(guildId,userId,ConsumerTeamRoleEnum.ORDINARY_MEMBER,null,Boolean.FALSE,Boolean.FALSE);
            // 公会人数+1
            this.guildMemberCountAdd(guild,1);
            // 作废已消耗
            this.guildApplyRecordService.cancelConsume(isApplyUnJoined,userId);
            // 执行已超时
            this.applyAndInviteTimeOutUser(userId);
            // 互动消息记录
            this.consumerInteractMsgService.record(userId,String.format("已加入【%s】公会~", guild.getName()),guildId,InteractMsgEnum.GUILD_JOIN_SUCCESS);
            // 消息推送：会员收到：已加入xx昵称公会（无跳转）
            SocketUtil.sendOneMessageDelayed(String.valueOf(userId), String.format("已加入【%s】公会~", guild.getName()), InteractMsgEnum.GUILD_JOIN_SUCCESS.getValue(), guildId);
            // 返回信息
            return R.ok(HttpStatus.JOIN_GUILD_OK,"加入公会成功~");//成功信息
        }
        // 需要审核，发送加入申请
        if (a1 || b1 || c1) {
            // 先要判断是否为消耗了材料；若消耗了材料，那么在加入申请A通过之前 的 任何加入申请都将不消耗材料；且申请任何公会的消耗材料状态都记为：已消耗
            GuildApplyConsumeStatusEnum gacs = isApplyUnJoined?GuildApplyConsumeStatusEnum.CONSUME_OK: GuildApplyConsumeStatusEnum.CONSUME_NO;
            // 记录用户的公会申请
            this.guildApplyRecordService.recordApply(guildId,userId, gacs);
            // 互动消息记录
            this.consumerInteractMsgService.record(guild.getCreateUserId(),String.format("【%s】申请加入公会~", SecurityUtils.getNickName()),userId,InteractMsgEnum.GUILD_JOIN_APPLY);
            // 消息推送：会长收到：xx用户昵称xx申请加入公会（点击跳转到【公会】页面）
            SocketUtil.sendOneMessageDelayed(String.valueOf(guild.getCreateUserId()), String.format("【%s】申请加入公会~", SecurityUtils.getNickName()), InteractMsgEnum.GUILD_JOIN_APPLY.getValue(), userId);
            // 返回信息
            return R.ok_msg("已发送加入申请~");//成功信息
        }
        //7 加入公会消耗材料后台配置了，返回前端，待用户确认消耗，确认消耗了，再 加入公会成功 或 已发送加入申请
        //"申请加入公会需要消耗材料~"
        return R.ok(vo,HttpStatus.JOIN_GUILD_CONSUME,null);
    }

    /**
     * 申请加入公会确认（需要消耗材料这一步）
     *
     * 需要注意的是这儿为了再次验证又调用了一次，申请加入公会的逻辑：{@link GuildAppService#applyJoinGuild(Long, Long)}，来获取加入公会需要消耗的材料信息
     * 当然也可以直接调用获取加入公会消耗材料的逻辑：{@link UserIdentityPermGuildService#getGuildJoinConsume(Long)}
     *
     * @param guildId 要加入的公会id
     * @param userId 加入公会的用户id
     * @param isVerify true-再次调用 申请加入公会 的逻辑进行验证
     * @return 有无开启审核（有-发送加入申请 无-直接加入公会）
     */
    @Transactional(rollbackFor = Exception.class)
    public R<Object> applyJoinGuildConfirmConsume(Long guildId,Long userId,boolean isVerify) {
        //1 获取加入的公会信息
        Guild guild = this.getGuildInfo(guildId);
        //  当前成员数大于等于最大成员数，满员
        Assert.isFalse(guild.getMemberCount()>=guild.getMaxNumber(),()->new ServiceException("申请公会已满员~"));
        //2 再去调用申请公会的逻辑（作用：再次验证，确认为需要消耗材料的逻辑），获取加入公会要消耗的材料
        GuildCreateConsumeVo vo = isVerify?(GuildCreateConsumeVo) this.applyJoinGuild(guildId,userId).getData():this.userIdentityPermGuildService.getGuildJoinConsume(userId);
        //3 主动退出公会冷却时间24小时
        GuildExitStatusEnum exitStatus = this.guildMemberService.getLastExitGuildInfo(userId);
        //4 进行判断
        //  验证有无开启审核（有-发送加入申请 无-直接加入公会）
        boolean isNeedVerify = guild.getNeedVerify();
        //- 若不在公会中且前一次主动退出公会，且后台【已配置】申请加入公会材料消耗，且公会【未开启】加入审核，单击【申请按钮】弹出【材料消耗】弹窗，确认消耗材料后加入公会成功，并提示：加入公会成功，并跳转到【公会】页面
        boolean a = exitStatus==GuildExitStatusEnum.EXIT_ACTIVE && !isNeedVerify;
        //- 若不在公会中且前一次主动退出公会，且后台【已配置】申请加入公会材料消耗，且公会【已开启】加入审核，单击【申请按钮】弹出【材料消耗】弹窗，确认消耗材料后加入公会成功，并提示：已发送加入申请
        boolean a1 = exitStatus==GuildExitStatusEnum.EXIT_ACTIVE && isNeedVerify;
        //- 若是第一次加入公会，且后台【已配置】申请加入公会材料消耗，且公会【未开启】加入审核，单击【申请按钮】弹出【材料消耗】弹窗，确认消耗材料后加入公会成功，并提示：加入公会成功，并跳转到【公会】页面
        boolean b = exitStatus==GuildExitStatusEnum.FIRST_JOIN_GUILD && !isNeedVerify;
        //- 若是第一次加入公会，且后台【已配置】申请加入公会材料消耗，且公会【已开启】加入审核，单击【申请按钮】弹出【材料消耗】弹窗，确认消耗材料后加入公会成功，并提示：已发送加入申请
        boolean b1 = exitStatus==GuildExitStatusEnum.FIRST_JOIN_GUILD && isNeedVerify;
        //5 不需要审核，直接加入公会
        if (a || b ) {
            // 消耗材料
            this.userIdentityPermGuildService.startConsume(vo,userId,Boolean.FALSE);
            // 直接加入公会
            this.guildMemberService.createMember(guildId,userId,ConsumerTeamRoleEnum.ORDINARY_MEMBER,null,Boolean.TRUE,Boolean.FALSE);
            // 公会人数+1
            this.guildMemberCountAdd(guild,1);
            // 执行已超时
            this.applyAndInviteTimeOutUser(userId);
            // 互动消息记录
            this.consumerInteractMsgService.record(userId,String.format("已加入【%s】公会~", guild.getName()),guildId,InteractMsgEnum.GUILD_JOIN_SUCCESS);
            // 消息推送：会员收到：已加入xx昵称公会（无跳转）
            SocketUtil.sendOneMessageDelayed(String.valueOf(userId), String.format("已加入【%s】公会~", guild.getName()), InteractMsgEnum.GUILD_JOIN_SUCCESS.getValue(), guildId);
            // 返回信息
            return R.ok(HttpStatus.JOIN_GUILD_OK,"加入公会成功~");//成功信息
        }
        //6 需要审核，发送加入申请
        if (a1 || b1 ) {
            // 消耗材料
            this.userIdentityPermGuildService.startConsume(vo,userId,Boolean.FALSE);
            // 记录用户的公会申请
            this.guildApplyRecordService.recordApply(guildId,userId,GuildApplyConsumeStatusEnum.CONSUME_OK);
            // 互动消息记录
            this.consumerInteractMsgService.record(guild.getCreateUserId(),String.format("【%s】申请加入公会~", SecurityUtils.getNickName()),userId,InteractMsgEnum.GUILD_JOIN_APPLY);
            // 消息推送：会长收到：xx用户昵称xx申请加入公会（点击跳转到【公会】页面）
            SocketUtil.sendOneMessageDelayed(String.valueOf(guild.getCreateUserId()), String.format("【%s】申请加入公会~", SecurityUtils.getNickName()), InteractMsgEnum.GUILD_JOIN_APPLY.getValue(), userId);
            // 返回信息
            return R.ok_msg("已发送加入申请~");//成功信息
        }
        throw new ServiceException("系统错误~");
    }

    /**
     * 申请记录 - 我的申请
     *
     * @param userId 用户id
     */
    public IPage<GuildMyApplyRecordVo> myApplyRecord(Page<GuildMyApplyRecordVo> page,Long userId) {
        // 我的申请

        return this.guildApplyRecordService.getMyApplyRecordVo(page,userId);
    }

    /**
     * 申请记录 - 邀请我的
     *
     * @param userId 用户id
     */
    public IPage<GuildInviteMeRecordVo> inviteMeRecord(Page<GuildInviteMeRecordVo> page,Long userId) {
        //邀请我的

        return this.guildInviteRecordService.getInviteMeRecordVo(page,userId);
    }


    /**
     * 加入邀请我的公会（邀请我的）
     *
     * 通过加入公会需要验证是否需要消耗材料
     *
     * 【1-通过】：
     *
     *          若不在公会中且前一次【主动退出】公会，且后台【已配置】加入消耗，单击【同意按钮】弹出【材料消耗】弹窗，确认消耗材料后加入公会成功，并提示：加入公会成功
     *
     *          若不在公会中且前一次【主动退出】公会，且后台【未配置】加入消耗，单击【同意按钮】后加入公会成功，并提示：加入公会成功
     *
     *          若不在公会中且前一次【非主动退出】公会或【申请被拒绝】，单击【同意按钮】后不消耗材料，直接加入公会成功，点击后提示：加入公会成功
     *
     *          若是第一次加入公会，且后台【已配置】加入消耗，单击【同意按钮】弹出【消耗材料】弹窗，确认消耗材料后加入公会成功，并提示：加入公会成功
     *
     *          若是第一次加入公会，且后台【未配置】加入消耗，单击【同意按钮】后加入公会成功，并提示：加入公会成功
     *
     *          主动退出同样受退出24小时冷却时间限制，单击【同意按钮】提示：冷却中，主动退出公会24小时后才能再次加入
     *
     * 【3-拒绝】：
     *
     *          拒绝后显示状态【已拒绝】
     *
     * @param guildId 公会id
     * @param status 状态（1-通过 3-拒绝）
     * @param userId 用户id
     */
    @Transactional(rollbackFor = Exception.class)
    public R<Object> joinInviteGuild(Long guildId, Integer status, Long userId) {
        // 获取邀请我的公会信息
        Guild guild = this.getGuildInfo(guildId);
        // 1-通过
        if (Objects.equals(status, GuildApplyOrInviteStatusEnum.JOINED.getValue())) {
            //1 判断通过邀请公会的用户是否实名
            Assert.notNull(this.consumerRealNameService.getUserRealNameOrNull(userId),()->new ServiceException("实名拥有身份后才能加入~"));
            //2 判断是否已在某个公会
            GuildMember member = this.guildMemberService.getGuildMemberInfo(userId);
            Assert.isFalse(ObjUtil.isNotEmpty(member),()->new ServiceException(member.getRole()==ConsumerTeamRoleEnum.ADMINISTRATOR?"会长不能直接加入其他公会~":"已在公会中，请退出后再加入~"));
            //3 当前成员数大于等于最大成员数，满员
            Assert.isFalse(guild.getMemberCount()>=guild.getMaxNumber(),()->new ServiceException("邀请公会已满员~"));
            //4 主动退出公会冷却时间24小时
            GuildExitStatusEnum exitStatus = this.guildMemberService.getLastExitGuildInfo(userId);
            //5 获取加入公会要消耗的材料
            GuildCreateConsumeVo vo = this.userIdentityPermGuildService.getGuildJoinConsume(userId);
            //6 进行判断
            // 是否存在之前的申请为待通过 或 已拒绝 或 已超时，且该次申请被消耗了材料，若消耗了材料，那么申请或加入其它公会将不消耗材料
            boolean isApplyUnJoined = this.guildApplyRecordService.isApplyUnJoined(userId);
            // 是否配置了加入公会消耗材料
            boolean noGuildJoinConsume = ObjUtil.isEmpty(vo);
            // 若不在公会中且前一次【主动退出】公会，且后台【未配置】加入消耗，单击【同意按钮】后加入公会成功，并提示：加入公会成功
            boolean a = exitStatus == GuildExitStatusEnum.EXIT_ACTIVE &&  noGuildJoinConsume;
            // 若不在公会中且前一次【非主动退出】公会或【申请被拒绝】，单击【同意按钮】后不消耗材料，直接加入公会成功，点击后提示：加入公会成功
            boolean b = exitStatus == GuildExitStatusEnum.EXIT_ACTIVE_UN || isApplyUnJoined;
            // 若是第一次加入公会，且后台【未配置】加入消耗，单击【同意按钮】后加入公会成功，并提示：加入公会成功
            boolean c = exitStatus == GuildExitStatusEnum.FIRST_JOIN_GUILD &&  noGuildJoinConsume;
            // 上面三种情况，直接加入公会
            if(a || b || c){
                // 执行通过-邀请我的公会
                this.guildInviteRecordService.updateInviteStatus(guildId,userId,GuildApplyOrInviteStatusEnum.JOINED);
                // 直接加入公会
                this.guildMemberService.createMember(guildId,userId,ConsumerTeamRoleEnum.ORDINARY_MEMBER,guild.getCreateUserId(),Boolean.FALSE,Boolean.FALSE);
                // 公会人数+1
                this.guildMemberCountAdd(guild,1);
                // 作废已消耗
                this.guildApplyRecordService.cancelConsume(isApplyUnJoined,userId);
                // 执行已超时
                this.applyAndInviteTimeOutUser(userId);
                // 互动消息记录
                this.consumerInteractMsgService.record(userId,String.format("已加入【%s】公会~", guild.getName()),guildId,InteractMsgEnum.GUILD_JOIN_SUCCESS);
                this.consumerInteractMsgService.record(guild.getCreateUserId(),String.format("【%s】已加入您的公会~", SecurityUtils.getNickName()),userId,InteractMsgEnum.GUILD_JOIN_SUCCESS);
                // 消息推送：会员收到：已加入xx昵称公会（无跳转） ,会长收到：xx用户昵称已加入您的公会
                SocketUtil.sendOneMessageDelayed(String.valueOf(userId), String.format("已加入【%s】公会~", guild.getName()), InteractMsgEnum.GUILD_JOIN_SUCCESS.getValue(), guildId);
                SocketUtil.sendOneMessageDelayed(String.valueOf(guild.getCreateUserId()), String.format("【%s】已加入您的公会~", SecurityUtils.getNickName()), InteractMsgEnum.GUILD_JOIN_SUCCESS.getValue(), userId);
                // 返回信息
                return R.ok(HttpStatus.JOIN_GUILD_OK,"加入公会成功~");//成功信息
            }
            // 否则就是后台配置了加入了公会要消耗的材料，返回前端，待用户确认消耗
            // "加入邀请我的公需要消耗材料~"
            return R.ok(vo, HttpStatus.JOIN_GUILD_CONSUME,null);
        }
        // 3-拒绝
        if (Objects.equals(status, GuildApplyOrInviteStatusEnum.REJECTED.getValue())) {
            // 执行拒绝-邀请我的公会
            this.guildInviteRecordService.updateInviteStatus(guildId,userId,GuildApplyOrInviteStatusEnum.REJECTED);
            // 互动消息记录
            this.consumerInteractMsgService.record(guild.getCreateUserId(),String.format("【%s】拒绝加入您的公会~", SecurityUtils.getNickName()),guildId,InteractMsgEnum.GUILD_JOIN_INVITE_REJECT_SUCCESS);
            // 消息推送：会长收到：【%s】拒绝加入您的公会~
            SocketUtil.sendOneMessageDelayed(String.valueOf(guild.getCreateUserId()), String.format("【%s】拒绝加入您的公会~", SecurityUtils.getNickName()), InteractMsgEnum.GUILD_JOIN_INVITE_REJECT_SUCCESS.getValue(), guildId);
            // 拒绝成功
            return R.ok_msg("已拒绝~");
        }
        return R.fail("操作状态不支持~");
    }

    /**
     * 邀请加入公会确认（需要消耗材料这一步）
     *
     * 需要注意的是这儿为了再次验证又调用了一次，邀请加入公会的逻辑：{@link GuildAppService#joinInviteGuild(Long, Integer, Long)}，来获取加入公会需要消耗的材料信息
     * 当然也可以直接调用获取加入公会消耗材料的逻辑：{@link UserIdentityPermGuildService#getGuildJoinConsume(Long)}
     *
     * @param guildId 要加入的公会id
     * @param userId 加入公会的用户id
     * @param isVerify true-再次调用 邀请加入公会 的逻辑进行验证
     * @return 有无开启审核（有-发送加入申请 无-直接加入公会）
     */
    @Transactional(rollbackFor = Exception.class)
    public R<Object> inviteJoinGuildConfirmConsume(Long guildId,Long userId,boolean isVerify) {
        //1 获取加入的公会信息
        Guild guild = this.getGuildInfo(guildId);
        //  当前成员数大于等于最大成员数，满员
        Assert.isFalse(guild.getMemberCount()>=guild.getMaxNumber(),()->new ServiceException("邀请公会已满员~"));
        //2 再去调用邀请加入公会的逻辑（作用：再次验证，确认为需要消耗材料的逻辑），获取加入公会要消耗的材料
        GuildCreateConsumeVo vo = isVerify?(GuildCreateConsumeVo) this.joinInviteGuild(guildId,GuildApplyOrInviteStatusEnum.JOINED.getValue(),userId).getData():this.userIdentityPermGuildService.getGuildJoinConsume(userId);
        //3 主动退出公会冷却时间24小时
        GuildExitStatusEnum exitStatus = this.guildMemberService.getLastExitGuildInfo(userId);
        //4 进行判断
        //- 若不在公会中且前一次【主动退出】公会，且后台【已配置】加入消耗，单击【同意按钮】弹出【材料消耗】弹窗，确认消耗材料后加入公会成功，并提示：加入公会成功
        boolean a = exitStatus==GuildExitStatusEnum.EXIT_ACTIVE;
        //- 若是第一次加入公会，且后台【已配置】加入消耗，单击【同意按钮】弹出【消耗材料】弹窗，确认消耗材料后加入公会成功，并提示：加入公会成功
        boolean b = exitStatus==GuildExitStatusEnum.FIRST_JOIN_GUILD;
        // 5 直接加入公会
        if (a || b ) {
            // 执行通过-邀请我的公会
            this.guildInviteRecordService.updateInviteStatus(guildId,userId,GuildApplyOrInviteStatusEnum.JOINED);
            // 消耗材料
            this.userIdentityPermGuildService.startConsume(vo,userId,Boolean.FALSE);
            // 直接加入公会
            this.guildMemberService.createMember(guildId,userId,ConsumerTeamRoleEnum.ORDINARY_MEMBER,guild.getCreateUserId(),Boolean.TRUE,Boolean.FALSE);
            // 公会人数+1
            this.guildMemberCountAdd(guild,1);
            // 执行已超时
            this.applyAndInviteTimeOutUser(userId);
            // 互动消息记录
            this.consumerInteractMsgService.record(userId,String.format("已加入【%s】公会~", guild.getName()),guildId,InteractMsgEnum.GUILD_JOIN_SUCCESS);
            this.consumerInteractMsgService.record(guild.getCreateUserId(),String.format("【%s】已加入您的公会~", SecurityUtils.getNickName()),userId,InteractMsgEnum.GUILD_JOIN_SUCCESS);
            // 消息推送：会员收到：已加入xx昵称公会（无跳转） ,会长收到：xx用户昵称已加入您的公会
            SocketUtil.sendOneMessageDelayed(String.valueOf(userId), String.format("已加入【%s】公会~", guild.getName()), InteractMsgEnum.GUILD_JOIN_SUCCESS.getValue(), guildId);
            SocketUtil.sendOneMessageDelayed(String.valueOf(guild.getCreateUserId()), String.format("【%s】已加入您的公会~", SecurityUtils.getNickName()), InteractMsgEnum.GUILD_JOIN_SUCCESS.getValue(), userId);
            // 返回信息
            return R.ok(HttpStatus.JOIN_GUILD_OK,"加入公会成功~");//成功信息
        }
        throw new ServiceException("系统错误~");
    }

    /**
     * 公会首页信息
     *
     * @param userId 已经加入公会的用户id（会长或会员）
     * @return 公会首页信息
     */
    public GuildVo guildIndexInfo(Long userId) {
        GuildMember member = this.guildMemberService.getGuildMemberInfo(userId);
        // 若没在公会中，给出提示
        Assert.notNull(member,()->new ServiceException("未在公会中~"));
        // 再获取
        return this.getBaseMapper().guildIndexInfo(member.getGuildId(),userId);
    }

    /**
     * 加入申请列表（会员申请公会的记录）
     *
     * 要求只能会长能查看
     *
     * @param userId 会长用户id
     * @return 加入申请列表
     */
    public IPage<GuildJoinApplyRecordVo> joinApplyList(Page<GuildJoinApplyRecordVo> page, Long userId) {
        // 验证
        Guild guild = this.getGuildOrNull(userId);
        Assert.notNull(guild,()->new ServiceException("只有会长能查看~"));
        // 获取加入申请
        return this.guildApplyRecordService.getJoinApplyList(page,guild.getId());
    }

    /**
     * 会长审核加入申请
     *
     * @param examineUserId 审核的用户id
     * @param status 处理状态只能是：（1-通过 3-拒绝）
     * @param userId 会长id
     */
    @Transactional(rollbackFor = Exception.class)
    public String examineGuildMember(Long examineUserId, Integer status, Long userId) {
        // 验证
        Guild guild = this.getGuildOrNull(userId);
        Assert.notNull(guild,()->new ServiceException("只有会长能操作~"));
        GuildMember member = this.guildMemberService.getGuildMemberInfo(examineUserId);
        Assert.isNull(member,()->new ServiceException(Objects.equals(guild.getId(), member.getGuildId()) ?"已在公会中~":"已在其它公会中~"));
        // 1-通过
        if (Objects.equals(status, GuildApplyOrInviteStatusEnum.JOINED.getValue())) {
            // 当前成员数大于等于最大成员数，满员
            Assert.isFalse(guild.getMemberCount()>=guild.getMaxNumber(),()->new ServiceException("申请公会已满员~"));
            // 执行同意-我的公会申请
            GuildApplyRecord record = this.guildApplyRecordService.updateApplyStatus(guild.getId(), examineUserId, GuildApplyOrInviteStatusEnum.JOINED);
            // 通过上一步获取该审核用户的申请记录，判断是否消耗了材料，如果消耗了材料，那么字段 isConsume设置为true，表示为消耗了材料
            boolean isConsumeApply = record.getConsumeStatus()==GuildApplyConsumeStatusEnum.CONSUME_OK;
            // 直接加入公会
            this.guildMemberService.createMember(guild.getId(),examineUserId,ConsumerTeamRoleEnum.ORDINARY_MEMBER,null,isConsumeApply,Boolean.FALSE);
            // 公会人数+1
            this.guildMemberCountAdd(guild,1);
            // 作废已消耗
            this.guildApplyRecordService.cancelConsume(Boolean.TRUE,examineUserId);
            // 执行已超时
            this.applyAndInviteTimeOutUser(examineUserId);
            // 互动消息记录
            this.consumerInteractMsgService.record(examineUserId,String.format("已加入【%s】公会~", guild.getName()),guild.getId(),InteractMsgEnum.GUILD_JOIN_SUCCESS);
            // 消息推送：会员收到：已加入xx昵称公会（无跳转）
            SocketUtil.sendOneMessageDelayed(String.valueOf(examineUserId), String.format("已加入【%s】公会~", guild.getName()), InteractMsgEnum.GUILD_JOIN_SUCCESS.getValue(), guild.getId());
            // 拒绝成功
            return "已同意~";
        }
        // 3-拒绝
        if (Objects.equals(status, GuildApplyOrInviteStatusEnum.REJECTED.getValue())) {
            // 执行拒绝-我的公会
            this.guildApplyRecordService.updateApplyStatus(guild.getId(),examineUserId,GuildApplyOrInviteStatusEnum.REJECTED);
            // 互动消息记录
            this.consumerInteractMsgService.record(examineUserId,String.format("申请加入公会【%s】已被拒绝~", guild.getName()),guild.getId(),InteractMsgEnum.GUILD_JOIN_APPLY_REJECT_SUCCESS);
            // 消息推送：会员收到：【%s】把【%s】转让给了您~（无跳转）
            SocketUtil.sendOneMessageDelayed(String.valueOf(examineUserId), String.format("申请加入公会【%s】已被拒绝~", guild.getName()), InteractMsgEnum.GUILD_JOIN_APPLY_REJECT_SUCCESS.getValue(), guild.getId());
            // 拒绝成功
            return "已拒绝~";
        }
        throw new ServiceException("操作状态不支持~");
    }

    /**
     * 公会大厅公会信息
     *
     * @param userId 用户id
     * @return 公会信息
     */
    public GuildHallVo guildHallInfo(Long userId) {
        GuildMember member = this.guildMemberService.getGuildMemberInfo(userId);
        // 若没在公会中，给出提示
        Assert.notNull(member,()->new ServiceException("未在公会中~"));
        // 再获取
        return this.getBaseMapper().guildHallInfo(member.getGuildId(),userId);
    }

    /**
     * 1.7.0 逻辑分页存在问题，这儿先采用下面物理分页（先查询公会全部成员）
     *
     * 公会大厅成员列表（分页查询）
     *
     * @param dto 传输对象
     * @param userId 用户id
     * @return 成员列表
     */
    public IPage<GuildMemberVo> guildHallMembers(GuildMemberDto<GuildMemberVo> dto, Long userId) {
        GuildMember member = this.guildMemberService.getGuildMemberInfo(userId);
        // 若没在公会中，给出提示
        Assert.notNull(member,()->new ServiceException("未在公会中~"));
        // 再获取
        return this.getBaseMapper().guildHallList(dto.newPage(),member.getGuildId(),userId,dto.getSearch());
    }

    /**
     * 1.7.0 逻辑分页存在问题，这儿先采用下面物理分页（先查询公会全部成员）
     *
     * 公会大厅成员列表（分页查询）
     *
     * @param dto 传输对象
     * @param userId 用户id
     * @return 成员列表
     */
    public IPage<GuildMemberVo> guildHallMemberss(GuildMemberDto<GuildMemberVo> dto, Long userId) {
        GuildMember member = this.guildMemberService.getGuildMemberInfo(userId);
        // 若没在公会中，给出提示
        Assert.notNull(member,()->new ServiceException("未在公会中~"));
        IPage<GuildMemberVo> iPage = new Page<>();
        // 获取公会的所有成员信息
        List<GuildMemberVo> vos = this.getBaseMapper().guildHallList(member.getGuildId(), userId, dto.getSearch());
        iPage.setTotal(vos.size());
        // 获取查询分页数
        int a = dto.getPageNum();
        int b = dto.getPageSize();
        int s = (a-1)*b;
        int e = Math.min(a * b, vos.size());
        // 分页
        if(s > vos.size()){
            return iPage;
        }
        vos = vos.subList(s, e);
        iPage.setRecords(vos);
        return iPage;
    }

    /**
     * 会员退出公会
     *
     *
     * @param userId 用户id
     */
    @Transactional(rollbackFor = Exception.class)
    public void memberExitGuild(Long userId) {
        // 获取要退出公会的成员信息
        GuildMember member = this.guildMemberService.getGuildMemberInfo(userId);
        // 若没在公会中，给出提示
        Assert.notNull(member,()->new ServiceException("未在公会中~"));
        // 获取公会信息
        Guild guild = this.getGuildInfo(member.getGuildId());
        // 会长不能退出公会
        Assert.isFalse(Objects.equals(guild.getCreateUserId(), userId),()->new ServiceException("会长不能退出公会~"));
        // 执行退出公会逻辑
        this.guildMemberService.exitGuild(guild.getId(),userId,GuildExitStatusEnum.EXIT_ACTIVE, GuildExitTypeEnum.ACTIVE);
        // 公会人数-1
        this.guildMemberCountSub(guild.getId(),1);
    }

    /**
     * 会长踢出会员
     *
     * @param kickUserId 被踢用户id（会员id）
     * @param userId 会长id
     */
    @Transactional(rollbackFor = Exception.class)
    public void memberKickGuild(Long kickUserId, Long userId) {
        // 获取要被退出公会的成员信息
        GuildMember member = this.guildMemberService.getGuildMemberInfo(kickUserId);
        // 若没在公会中，给出提示
        Assert.notNull(member,()->new ServiceException("被踢人未在公会中~"));
        // 获取踢人的会长所在的公会信息
        Guild guild = this.getGuildOrNull(userId);
        // 不是会长不能踢人
        Assert.notNull(guild,()->new ServiceException("只有会长能踢人~"));
        // 若被踢人不和队长在一个公会，则不能踢出
        Assert.isFalse(!Objects.equals(guild.getId(), member.getGuildId()),()->new ServiceException("不能踢出其它公会的会员~"));
        // 会长不能踢出自己
        Assert.isFalse(Objects.equals(guild.getCreateUserId(), kickUserId),()->new ServiceException("会长不能踢出自己~"));
        // 执行退出公会逻辑
        this.guildMemberService.exitGuild(guild.getId(),kickUserId,GuildExitStatusEnum.EXIT_ACTIVE_UN, GuildExitTypeEnum.KICKED);
        // 公会人数-1
        this.guildMemberCountSub(guild.getId(),1);
        // 互动消息记录
        this.consumerInteractMsgService.record(kickUserId,String.format("您已被请出【%s】公会~", guild.getName()),guild.getId(),InteractMsgEnum.GUILD_JOIN_SUCCESS);
        // 消息推送：会员收到：您已被请出xx昵称公会（无跳转）
        SocketUtil.sendOneMessageDelayed(String.valueOf(kickUserId), String.format("您已被请出【%s】公会~", guild.getName()), InteractMsgEnum.GUILD_JOIN_SUCCESS.getValue(), guild.getId());
    }

    /**
     * 会长编辑公会名称
     *
     * @param guildName 新公会名称
     * @param userId 会长用户id
     */
    @Transactional(rollbackFor = Exception.class)
    public void editGuildName(String guildName, Long userId) {
        // 获取编辑公会名称的公会信息
        Guild guild = this.getGuildOrNull(userId);
        // 若为空，则不是会长，无权限编辑
        Assert.notNull(guild,()->new ServiceException("只有会长能编辑公会名称~"));
        // 公会名称验重
        Assert.isFalse(this.lambdaQuery().eq(Guild::getName,guildName).eq(Guild::getStatus,0).ne(Guild::getId,guild.getId()).count()!=0,()->new ServiceException("名称重复，换个试试~"));
        // 公会名称未修改
        Assert.isFalse(Objects.equals(guildName,guild.getName()),()->new ServiceException("修改失败，名称未发生变动~"));
        // 编辑公会名称，消耗热豆
        BigDecimal consume = this.sysConfigService.getValue(Config.Sys.Guild.编辑公会名称热豆消耗数量);
        // 扣减热豆
        this.sysUserWalletService.reduce(userId, UserWalletEnum.INTEGRAL,consume,  WalletChangeTypeEnum.EDIT_GUILD_NAME,userId,"修改公会名称");
        // 修改公会名称
        boolean update = this.lambdaUpdate().set(Guild::getName, guildName).eq(Guild::getId, guild.getId()).update();
        // 验证
        Assert.isTrue(update,()->new ServiceException("编辑公会名称失败~"));
    }

    /**
     * 会长编辑公会宣言
     *
     * @param manifesto 新公会宣言
     * @param userId 会长用户id
     */
    @Transactional(rollbackFor = Exception.class)
    public void editGuildManifesto(String manifesto, Long userId) {
        // 获取编辑公会名称的公会信息
        Guild guild = this.getGuildOrNull(userId);
        // 若为空，则不是会长，无权限编辑
        Assert.notNull(guild,()->new ServiceException("只有会长能编辑公会宣言~"));
        // 修改公会宣言
        boolean update = this.lambdaUpdate().set(Guild::getManifesto, manifesto).eq(Guild::getId, guild.getId()).update();
        // 验证
        Assert.isTrue(update,()->new ServiceException("编辑公会宣言失败~"));
    }

    /**
     * 会长编辑公会是否开启审核
     *
     * @param isNeedVerify 新公会宣言
     * @param userId 会长用户id
     */
    @Transactional(rollbackFor = Exception.class)
    public void editGuildVerify(Integer isNeedVerify, Long userId) {
        // 获取编辑公会是否开启审核的公会信息
        Guild guild = this.getGuildOrNull(userId);
        // 若为空，则不是会长，无权限编辑
        Assert.notNull(guild,()->new ServiceException("只有会长能编辑公会宣言~"));
        // 修改公会审核状态
        boolean update = this.lambdaUpdate().set(Guild::getNeedVerify, isNeedVerify).eq(Guild::getId, guild.getId()).update();
        // 验证
        Assert.isTrue(update,()->new ServiceException("审核状态更新失败~"));
    }

    /**
     * 邀请会员列表
     *
     * 随机排序，列表过滤当前用户和已再公会的用户信息
     *
     * @param dto 传输对象
     * @param userId 会长用户id
     * @return 邀请会员列表
     */
    public IPage<GuildInviteMemberVo> inviteMembersList(GuildMemberDto<GuildInviteMemberVo> dto, Long userId) {
        // 获取公会信息
        Guild guild = this.getGuildOrNull(userId);
        // 判断是否为会长
        Assert.notNull(guild,()->new ServiceException("只有会长能够进行邀请~"));
        // 分页查询
        return this.getBaseMapper().inviteMembersList(dto.newPage(),guild.getId(),userId,dto.getSearch());
    }

    /**
     * 会长邀请会员，可邀请多个
     *
     * @param userIds 邀请的会员用户id集
     * @param userId 会长用户id
     */
    @Transactional(rollbackFor = Exception.class)
    public void inviteMembers(List<String> userIds, Long userId) {
        // 转换
        Set<Long> set = userIds.stream().map(Long::parseLong).collect(Collectors.toSet());
        //判断用户列表是否都存在
        set.forEach(inviteUserId->Assert.isFalse(ObjUtil.isNull(userService.selectUserById(inviteUserId)),()->new ServiceException(StringUtils.format("{}用户不存在！", inviteUserId))));
        //判断用户列表中是否有用户已创建了公会
        List<GuildMember> members = this.guildMemberService.lambdaQuery().select(GuildMember::getGuildId, GuildMember::getUserId).in(GuildMember::getUserId, userIds).list();
        Assert.isFalse(CollUtil.isNotEmpty(members),()->new ServiceException("用户[" + members.stream().map(GuildMember::getUserId).map(String::valueOf).collect(Collectors.joining(",")) + "]已加入其它公会！"));
        // 获取公会信息
        Guild guild = this.getGuildOrNull(userId);
        // 判断是否为会长
        Assert.notNull(guild,()->new ServiceException("只有会长能够进行邀请~"));
        // 当前成员数大于等于最大成员数，满员
        Assert.isFalse(guild.getMemberCount()>=guild.getMaxNumber(),()->new ServiceException("公会已满员~"));
        // 发送邀请记录
        set.forEach(invitedUserId->this.guildInviteRecordService.recordInvite(guild.getId(),userId,invitedUserId));
        // 互动消息记录
        set.forEach(invitedUserId->this.consumerInteractMsgService.record(invitedUserId,String.format("【%s】邀请您加入【%s】公会~", SecurityUtils.getNickName(),guild.getName()),guild.getId(),InteractMsgEnum.GUILD_JOIN_INVITE));
        // 消息推送：会员收到：xx邀请加入xx昵称公会（点击跳转到【公户推荐】页面）
        set.forEach(invitedUserId->SocketUtil.sendOneMessageDelayed(String.valueOf(invitedUserId), String.format("【%s】邀请您加入【%s】公会~",SecurityUtils.getNickName(),guild.getName()), InteractMsgEnum.GUILD_JOIN_INVITE.getValue(), guild.getId()));
    }

    /**
     * 会长解散公会
     *
     * @param userId 会长id
     */
    @Transactional(rollbackFor = Exception.class)
    public void dissolveGuild(Long userId) {
        // 获取公会信息
        Guild guild = this.getGuildOrNull(userId);
        // 判断是否为会长
        Assert.notNull(guild,()->new ServiceException("只有会长能够解散公会~"));
        // 判断公会人数
        Assert.isFalse(guild.getMemberCount()>1,()->new ServiceException("你可以转让公会~"));
        // 执行解散公会逻辑
        boolean update = this.lambdaUpdate()
                .set(Guild::getStatus, 1)
                .set(Guild::getMemberCount, 0)
                .eq(Guild::getId, guild.getId())
                .update();
        // 执行退出公会逻辑
        this.guildMemberService.exitGuild(guild.getId(),userId,GuildExitStatusEnum.EXIT_ACTIVE,GuildExitTypeEnum.APP_DISSOLVE);
        // 验证
        Assert.isTrue(update,()->new ServiceException("解散公会失败~"));
        // 执行已超时
        this.applyAndInviteTimeOutGuild(guild.getId());
        // 互动消息记录
        this.consumerInteractMsgService.record(userId,String.format("您已解散【%s】公会~", guild.getName()),guild.getId(),InteractMsgEnum.GUILD_DISSOLVE_SUCCESS);
    }

    /**
     * 转让公会-会员列表（不包括会长）
     *
     * @param dto 传输对象
     * @param userId 会长用户id
     * @return 转让公会-会员列表
     */
    public IPage<GuildTransferMemberVo> transferMembersList(GuildMemberDto<GuildTransferMemberVo> dto, Long userId) {
        // 获取公会信息
        Guild guild = this.getGuildOrNull(userId);
        // 判断是否为会长
        Assert.notNull(guild,()->new ServiceException("只有会长能够转让公会~"));
        // 查询
        return this.getBaseMapper().transferMembersList(dto.newPage(),guild.getId(),userId,dto.getSearch());
    }

    /**
     * 会长转让会长
     *
     * @param transferUserId 转让的会长用户id（会员id）
     * @param userId 会长用户id
     */
    @Transactional(rollbackFor = Exception.class)
    public void transferGuild(Long transferUserId, Long userId) {
        // 获取转让的会长成员信息
        GuildMember member = this.guildMemberService.getGuildMemberInfo(transferUserId);
        // 为空，则：转让的会长未在公会中~
        Assert.notNull(member,()->new ServiceException("转让的会长未在公会中~"));
        // 获取公会信息
        Guild guild = this.getGuildOrNull(userId);
        // 判断是否为会长
        Assert.notNull(guild,()->new ServiceException("只有会长能够转让公会~"));
        // 若转让的会长所在的公会id，不和当前会长所在公会相同，则：不能把公会转让给其它公会会员~
        Assert.isFalse(!Objects.equals(member.getGuildId(), guild.getId()),()->new ServiceException("不能把公会转让给其它公会会员~"));
        // 会长成为会员
        boolean a = this.guildMemberService.lambdaUpdate().set(GuildMember::getRole, ConsumerTeamRoleEnum.ORDINARY_MEMBER).eq(GuildMember::getGuildId, guild.getId()).eq(GuildMember::getUserId,userId).update();
        // 执行公会转让逻辑，更新公会创建人
        boolean b = this.lambdaUpdate().set(Guild::getCreateUserId, transferUserId).eq(Guild::getId, guild.getId()).update();
        // 会员成为会长
        boolean c = this.guildMemberService.lambdaUpdate().set(GuildMember::getRole, ConsumerTeamRoleEnum.ADMINISTRATOR).eq(GuildMember::getId, member.getId()).update();
        // 验证
        Assert.isFalse( !a || !b || !c,()->new ServiceException("转让会长失败~"));
        // 互动消息记录
        this.consumerInteractMsgService.record(transferUserId,String.format("【%s】把公会【%s】转让给了您~", SecurityUtils.getNickName(),guild.getName()),guild.getId(),InteractMsgEnum.GUILD_TRANSFER_SUCCESS);
        // 消息推送：会员收到：【%s】把公会【%s】转让给了您~（无跳转）
        SocketUtil.sendOneMessageDelayed(String.valueOf(transferUserId), String.format("【%s】把公会【%s】转让给了您~", SecurityUtils.getNickName(),guild.getName()), InteractMsgEnum.GUILD_TRANSFER_SUCCESS.getValue(), guild.getId());
    }

    //*************************************************************************************************************
    //************************************TODO：下面是封装相关********************************************************
    //*************************************************************************************************************

    /**
     * 通过用户id获取公会信息，没有则返回空
     *
     * @param userId 用户id
     * @return 公会信息
     */
    public Guild getGuildOrNull(Long userId){
        Optional.ofNullable(userId).orElseThrow(()->new ServiceException("用户id不能为空"));
        Guild guild = this.lambdaQuery().eq(Guild::getCreateUserId, userId).eq(Guild::getStatus,0).one();
        return Optional.ofNullable(guild).orElse(null);
    }

    /**
     *
     * 通过公会id获取公会信息
     *
     * @param guildId 公会id
     * @return 公会信息
     */
    private Guild getGuildInfo(Long guildId) {
        Optional.ofNullable(guildId).orElseThrow(()->new ServiceException("公会id不能为空~"));
        Guild guild = this.getById(guildId);
        Optional.ofNullable(guild).orElseThrow(()->new ServiceException("未查询到公会信息~"));
        return guild;
    }

    /**
     * 创建公会和会长
     *
     * @param dto 传输对戏
     * @param userId 用户id
     * @param isConsume 进入公会是否消耗材料（true-消耗）
     */
    @Transactional(rollbackFor = Exception.class)
    public void createGuildAndMember(GuildCreateDto dto, Long userId,Boolean isConsume) {
        //设置这几个属性，其它属性走数据库默认值
        Guild guild = Guild.builder().createUserId(userId).name(dto.getName()).memberCount(1).manifesto(dto.getManifesto()).needVerify(dto.getNeedVerify()).build();
        //新增公会
        boolean save = this.save(guild);
        //验证
        Assert.isTrue(save, () -> new ServiceException(String.format("用户【%s】创建公会失败！", userId)));
        //创建会长
        this.guildMemberService.createMember(guild.getId(),userId,ConsumerTeamRoleEnum.ADMINISTRATOR,null,isConsume,Boolean.TRUE);
        //上面执行成功后，说明已创建好公会，那么把自己申请和别人邀请的未处理的置为已超时
        this.applyAndInviteTimeOutUser(userId);
    }


    /**
     * 公会人数添加数量
     *
     * @param guild 公会信息
     * @param num 添加的数量
     */
    @Transactional(rollbackFor = Exception.class)
    public void guildMemberCountAdd(Guild guild,int num) {
        Assert.isFalse(num<=0,()->new ServiceException("添加公会成员数量必须大于0~"));
        //公会如果满员，执行已超时
        if(guild.getMemberCount()+num >= guild.getMaxNumber())this.applyAndInviteTimeOutGuild(guild.getId());
        boolean update = this.getBaseMapper().guildMemberCountAdd(guild.getId(), num);
        Assert.isTrue(update,()->new ServiceException("公会人数添加失败~"));
    }

    /**
     * 公会人数减少数量
     *
     * @param guildId 公会id
     * @param num 添加的数量
     */
    @Transactional(rollbackFor = Exception.class)
    public void guildMemberCountSub(Long guildId,int num) {
        Assert.isFalse(num<=0,()->new ServiceException("减少公会成员数量必须大于0~"));
        boolean update = this.getBaseMapper().guildMemberCountSub(guildId, num);
        Assert.isTrue(update,()->new ServiceException("公会人数减少失败~"));
    }


    /**
     * 【待通过状态置为已超时】-若用户已加入到公会
     *
     * @param userId 已加入到公会的用户id
     */
    @Transactional(rollbackFor = Exception.class)
    public void applyAndInviteTimeOutUser(Long userId) {
        //1 用户申请的其它所有公会记录中，待通过状态置为已超时
        this.guildApplyRecordService.actionApplyTimeoutUser(userId);
        //2 邀请了该用户的所有公会的邀请记录，待通过状态置为已超时
        this.guildInviteRecordService.actionInviteTimeOutUser(userId);
    }

    /**
     * 【待通过状态置为已超时】-公会解散或公会已满员
     *
     * @param guildId 公会解散或公会已满员 的公会id
     */
    @Transactional(rollbackFor = Exception.class)
    public void applyAndInviteTimeOutGuild(Long guildId) {
        //1 该公会所有申请过的用户的申请记录，待通过状态置为已超时
        this.guildApplyRecordService.actionApplyTimeoutGuild(guildId);
        //2 该公会所有邀请过的用户的邀请记录，待通过状态置为已超时
        this.guildInviteRecordService.actionInviteTimeOutGuild(guildId);
    }

    /**
     *
     * socket消息推送弹窗关闭，根据配置的时间，设置关闭时间
     *
     * @param userId 用户id
     */
    public void closeSocketMsg(Long userId) {
        Integer minute = this.sysConfigService.getValue(Config.Sys.Guild.消息推送弹窗关闭时长);
        redisCache.setCacheObject(CacheConstants.KEY_USER_MSG_PUSH_CLOSE_TIME+userId,minute,minute, TimeUnit.MINUTES);
    }
}
